//
// GPSMapEdit
// (c) Konstantin Galichsky (kg@geopainting.com), 2002-2007
//
// Object type sets stuff.
//

# include "StdAfx.h"
# include "Resource.h"
# include "Types.h"
# include "TypesDraw.h"
# include "TypesPOIDraw.h"
# include "TypeLocalization.h"
# include "GarminTypes.h"
# include "HoluxTypes.h"
# include "Map.h"
# include "Globals.h"

const char * g_strKinds [rkMax];
type_desc_t g_tdUnknown;

///////////////////////////////////////////////

void InitTypes () {
	g_strKinds [rkPoint]    = GetStr (IDS_POINT);
	g_strKinds [rkPolyline] = GetStr (IDS_POLYLINE);
	g_strKinds [rkPolygon]  = GetStr (IDS_POLYGON);

	g_tdUnknown.Category       = catUnknown;
	g_tdUnknown.strTypeName    = "Unknown";
	g_tdUnknown.phBrush        = & g_hbrUnknown;
	g_tdUnknown.phPen          = & g_hpenUnknown;
	g_tdUnknown.phPen2         = NULL;
	g_tdUnknown.hPenGeometric  = NULL;
	g_tdUnknown.phFont         = NULL;
	g_tdUnknown.colText        = c_colBlack;
	g_tdUnknown.phCursor       = & g_hcurUnknown;
	g_tdUnknown.pfPrepareLabel = PreparePointLabel;
	g_tdUnknown.pfDrawLabel    = DrawLabel;

	InitCommonTypesGDI ();

	for (CTypeSet * pTypeSet = CTypeSet::s_pFirst; pTypeSet; pTypeSet = pTypeSet->m_pNext) {
		pTypeSet->m_pfInit ();
		pTypeSet->InitSecondaryAttributes ();
	}

	// Prepare localized names for all types.
	LoadTypeSetLocalization ();
}

void DestroyTypes () {
	for (CTypeSet * pTypeSet = CTypeSet::s_pFirst; pTypeSet; pTypeSet = pTypeSet->m_pNext) {
		pTypeSet->DestroySecondaryAttributes ();
		pTypeSet->m_pfDestroy ();
	}

	DestroyCommonTypesGDI ();
}

///////////////////////////////////////////////

CTypeSet * CTypeSet::s_pFirst;
CTypeSetConvertorInfo *  CTypeSetConvertorInfo::s_pFirst;

CTypeSet::CTypeSet (pfInit_t _pfInit, pfInit_t _pfDestroy) :
	m_pfInit (_pfInit),
	m_pfDestroy (_pfDestroy),
	pBasicTypeSet (NULL),
	bLocalizationIsReady (false),
	bHasLocalizedTypes (false)
{
	// Insert to the head of the list.
	m_pNext = CTypeSet::s_pFirst;
	CTypeSet::s_pFirst = this;
}

const CTypeSet * CTypeSet::FindTypeSet (const char * _strID) {
	for (const CTypeSet * pTypeSet = CTypeSet::s_pFirst; pTypeSet; pTypeSet = pTypeSet->m_pNext) {
		if (::stricmp (_strID, pTypeSet->strID) == 0)
			return pTypeSet;
	}
	return & g_tsNull;
}

USHORT ConvertNullTypeSet (RGN_KIND _Kind, USHORT _ushType) {return _ushType;}

pfConvertTypeSet_t CTypeSet::FindConvertTypeSetTo (const CTypeSet * _pTypeSetTo) const {
	if (this == & g_tsNull)
		return ConvertNullTypeSet;

	for (const CTypeSetConvertorInfo * p = CTypeSetConvertorInfo::s_pFirst; p; p = p->m_pNext) {
		if (p->pTypeSetFrom == this && p->pTypeSetTo == _pTypeSetTo)
			return p->pfConvertTypeSet;
	}
	return NULL;
}

///////////////////////////////////////////////

const type_desc_t * CTypeSet::GetTypeDesc (USHORT _shType, USHORT _ushKind) const {
	if (cTypes <= 1)
		return GetUnknownTD ();

	const ULONG ulType = _shType + (ULONG (_ushKind) << 16);
	
	size_t a = 1, b = cTypes - 1;
	while (true) {
		assert (a < cTypes);
		const ULONG ulA = pTypes [a].ulType;
		if (ulA == ulType)
			return & pTypes [a];

		assert (b < cTypes);
		const ULONG ulB = pTypes [b].ulType;
		if (ulB == ulType)
			return & pTypes [b];

		if (b - a <= 1) {
			if (ulA < ulType && ulType <= pTypes [a].ulType2)
				return & pTypes [a];

			return GetUnknownTD ();
		}

		const size_t m = (a + b)/2;

		assert (m < cTypes);
		const ULONG ulM = pTypes [m].ulType;
		if (ulM < ulType)
			a = m;
		else
			b = m;
	}
}

void CTypeSet::InitSecondaryAttributes () {
	// Check type ranges for consistency.
	size_t cType;
	for (cType = 0; cType < cTypes - 1; ++ cType) {
		type_desc_t & td0 = pTypes [cType];
		type_desc_t & td1 = pTypes [cType + 1];

		assert (td0.ulType <= td0.ulType2);
		assert (td0.ulType2 < td1.ulType);
	}

	// Prepare tmp DC to determine color of bitmap brush.
	const CClientDC hWndDC (NULL);
	const CDCHolder hdcTmp = ::CreateCompatibleDC (hWndDC);
	const CBitmapHolder hBmp = ::CreateCompatibleBitmap (hWndDC, 1, 1);
	::SelectObject (hdcTmp, hBmp);

	for (cType = 0; cType < cTypes; ++ cType) {
		// Check metainfo.
		if (cType < cTypes - 1)
			assert (pTypes [cType].ulType < pTypes [cType + 1].ulType);

		type_desc_t & td = pTypes [cType];

		td.bUnknown = cType == 0;

		td.pTypeSet = this;
		td.cOrdinal = cType;

		// Prepare cosmetic pen (for printing).
/*		if (td.phPen) {
			LOGPEN lp;
			if (::GetObject (* td.phPen, sizeof (lp), & lp) >= sizeof (lp)) {
				LOGBRUSH lb;
				lb.lbStyle = BS_SOLID;
				lb.lbColor = lp.lopnColor;
				lb.lbHatch = 0;
				td.hPenGeometric = ::ExtCreatePen (
					PS_GEOMETRIC | lp.lopnStyle | PS_ENDCAP_FLAT | PS_JOIN_ROUND,
					lp.lopnWidth.x, & lb, 0, NULL
				);
			}
		}*/

		// Create brushes for transparent polygons.
		if (td.Kind == rkPolygon || cType == 0) {
			assert (td.phBrush);
			if (* td.phBrush == g_hbrNull) {
				td.hpenTransparentPolygon = g_hpenGrayThin;
				td.hbrTransparentPolygon  = g_hbrNull;
			} else {
				LOGBRUSH lb;
				::memset (& lb, 0, sizeof (lb));
				::GetObject (* td.phBrush, sizeof (lb), & lb);
				if (lb.lbStyle == BS_SOLID || lb.lbStyle == BS_HATCHED) {
					td.hpenTransparentPolygon = ::CreatePen (PS_SOLID, 1, lb.lbColor);
					td.hbrTransparentPolygon  = ::CreateHatchBrush (HS_DIAGCROSS, lb.lbColor);
				} else {
					// Get color of (0,0)-th point in the brush pattern.
					::SelectObject (hdcTmp, * td.phBrush);
					::PatBlt (hdcTmp, 0, 0, 1, 1, PATCOPY);
					const COLORREF col0 = ::GetPixel (hdcTmp, 0, 0);

					td.hpenTransparentPolygon = ::CreatePen (PS_SOLID, 1, col0);
					td.hbrTransparentPolygon  = ::CreateHatchBrush (HS_DIAGCROSS, col0);
				}
			}
		} else
			td.hbrTransparentPolygon = NULL;
	}
}

void CTypeSet::DestroySecondaryAttributes () {
	for (size_t cType = 0; cType < cTypes; ++ cType) {
		type_desc_t & td = pTypes [cType];

		if (td.hPenGeometric)
			::DeleteObject (td.hPenGeometric);
		if (td.hpenTransparentPolygon)
			::DeleteObject (td.hpenTransparentPolygon);
		if (td.hbrTransparentPolygon)
			::DeleteObject (td.hbrTransparentPolygon);
	}

	if (hilTypeIcons)
		::ImageList_Destroy (hilTypeIcons);
}

/////////////////////////////////////////////////

HIMAGELIST CTypeSet::GetTypeIcons () const {
	if (hilTypeIcons == NULL)
		const_cast<CTypeSet *> (this)->_InitTypeIcons ();
	return hilTypeIcons;
}

void CTypeSet::_InitTypeIcons () {
	if (cTypes == 0)
		return;

	//
	// Create image lists for all kinds.
	//

	const size_t c_cIconW = 16, c_cIconH = 16;
	hilTypeIcons = ::ImageList_Create (c_cIconW, c_cIconH, ILC_COLORDDB, 64, 64);

	// Prepare buffer bitmap.
	const CClientDC hdcScreen (NULL);
	const CBitmapHolder hbmpTmp = ::CreateCompatibleBitmap (hdcScreen, c_cIconW, c_cIconH);
	const CDCHolder hdcMem = ::CreateCompatibleDC (hdcScreen);
	::SelectObject (hdcMem, hbmpTmp);
	::SetROP2 (hdcMem, R2_COPYPEN);
	::SetBkMode (hdcMem, TRANSPARENT);
	::SetBrushOrgEx (hdcMem, 1, 1, NULL);

	// Render the icons.
	for (size_t cType = 0; cType < cTypes; ++ cType) {
		// Clean background.
		const RECT rect = {0, 0, c_cIconW, c_cIconH};
		::FillRect (hdcMem, & rect, ::GetSysColorBrush (COLOR_WINDOW));

		assert (pTypes [cType].Kind < rkMax);
		const RGN_KIND Kind = static_cast<RGN_KIND> (pTypes [cType].Kind);

		switch (Kind) {
			case rkPoint:
				assert (pTypes [cType].phCursor);
				::DrawIcon (hdcMem, 0, 0, * pTypes [cType].phCursor);

				break;

			case rkPolyline:
				// Render with the first pen.
				assert (pTypes [cType].phPen);
				::SelectObject (hdcMem, * pTypes [cType].phPen);
				::MoveToEx (hdcMem, 0,        c_cIconH/2, NULL);
				::LineTo   (hdcMem, c_cIconW, c_cIconH/2);

				// Render with the second pen, if any.
				if (pTypes [cType].phPen2) {
					::SelectObject (hdcMem, * pTypes [cType].phPen2);
					::MoveToEx (hdcMem, 0,        c_cIconH/2, NULL);
					::LineTo   (hdcMem, c_cIconW, c_cIconH/2);
				}

				break;

			case rkPolygon:
				// Render sample.
				assert (pTypes [cType].phPen);
				::SelectObject (hdcMem, * pTypes [cType].phPen);
				assert (pTypes [cType].phBrush);
				::SelectObject (hdcMem, * pTypes [cType].phBrush);

				::Rectangle (hdcMem, 0, 0, c_cIconW, c_cIconH);

				break;
		}

		ICONINFO ii = {TRUE, 0, 0, hbmpTmp, hbmpTmp};
		const HICON hicon = ::CreateIconIndirect (& ii);

		pTypes [cType].cImageListIndex = ::ImageList_AddIcon (hilTypeIcons, hicon);

		::DestroyIcon (hicon);
	}
	pTypes [0].cImageListIndex = 0;
}

/////////////////////////////////////////////////

void type_desc_t::GetCursorShift (POINT & _ptShift) const {
	if (! bCursorShiftInitialized) {
		const_cast<bool &> (bCursorShiftInitialized) = true;

		if (phCursor) {
			ICONINFO ii;
			::memset (& ii, 0, sizeof (ii));
			::GetIconInfo (* phCursor, & ii);
			const_cast<long &> (ptCursorShift.x) = ii.xHotspot;
			const_cast<long &> (ptCursorShift.y) = ii.yHotspot;
			::DeleteObject (ii.hbmColor);
			::DeleteObject (ii.hbmMask);
		} else {
			const_cast<long &> (ptCursorShift.x) = 0;
			const_cast<long &> (ptCursorShift.y) = 0;
		}
	}

	_ptShift = ptCursorShift;
}

/////////////////////////////////////////////////

static type_desc_t g_TypeDescsOfNullTypeSet [1];

static
void InitNullTypes () {
	g_TypeDescsOfNullTypeSet [0] = g_tdUnknown;

	g_tsNull.pTypes           = g_TypeDescsOfNullTypeSet;
	g_tsNull.cTypes           = 1;

	g_tsNull.strID            = "Null";
	g_tsNull.strDisplayName   = "Null";

	g_tsNull.pfGetLatGrid     = DontRoundGrid;
	g_tsNull.pfRoundPoint     = DontRoundPoint;
	g_tsNull.pfRoundPoints    = DontRoundPoints;
	g_tsNull.pfRoundRect      = DontRoundRect;

	g_tsNull.pfIsCity         = RgnPredicateFalse;
	g_tsNull.pfIsHwExit       = RgnPredicateFalse;
	g_tsNull.pfIsRoad         = RgnPredicateFalse;
	g_tsNull.pfIsUnpavedRoad  = RgnPredicateFalse;
	g_tsNull.pfIsBackground   = RgnPredicateFalse;
	g_tsNull.pfHasAddress     = RgnPredicateFalse;
	g_tsNull.pfHasCityAndZip  = RgnPredicateFalse;
	g_tsNull.pfHasPhone       = RgnPredicateFalse;
	g_tsNull.pfHasContact     = RgnPredicateFalse;
	g_tsNull.pfHasDescription = RgnPredicateFalse;

	g_tsNull.dwFeatures       = 0;

	g_tsNull.cMaxBits         = 24;
}

static
void DestroyNullTypes () {}

CTypeSet g_tsNull (InitNullTypes, DestroyNullTypes);

/////////////////////////////////////////////////

struct category_info_t {
	const char * strDisplayName;
	COLORREF col;
};

static
category_info_t g_TypeCategories [] = {
	{"Roads",          RGB (240, 240, 240)},
	{"Boundaries",     c_colWhite},
	{"Elevation",      RGB (255, 255, 230)},
	{"Cities",         RGB (230, 230, 230)},
	{"Labels",         c_colWhite},
	{"Food",           RGB (255, 255, 230)},
	{"Lodging",        c_colWhite},
	{"Attractions",    c_colWhite},
	{"Entertainment",  c_colWhite},
	{"Shopping",       c_colWhite},
	{"Services",       RGB (240, 240, 255)},
	{"Emerg./Gov.",    RGB (255, 230, 230)},
	{"Transport",      RGB (230, 230, 230)},
	{"Religion",       RGB (255, 255, 230)},
	{"Education",      c_colWhite},
	{"Sports",         RGB (255, 240, 240)},
	{"Recreation",     RGB (230, 255, 240)},
	{"Water",          RGB (230, 230, 255)},
	{"Land",           RGB (230, 255, 230)},
	{"Man made",       RGB (230, 230, 230)},
	{"Building",       RGB (254, 225, 209)},
	{"Auto",           RGB (230, 230, 230)},
	{"Aviation",       c_colWhite},
	{"Marine",         RGB (230, 230, 255)},
	{"Marine navaids", RGB (240, 240, 255)},
	{"Marine danger",  RGB (230, 230, 255)},
	{"Others",         c_colWhite},
	{"Unknown",        RGB (255, 230, 230)},
};

const char * GetTypeCategoryDisplayName (type_category_t _tc) {
	if (_tc >= sizeof (g_TypeCategories)/sizeof (g_TypeCategories [0]))
		return "";
	return g_TypeCategories [_tc].strDisplayName;
}

COLORREF GetTypeCategoryColor (type_category_t _tc) {
	if (_tc >= sizeof (g_TypeCategories)/sizeof (g_TypeCategories [0]))
		return c_colWhite;
	return g_TypeCategories [_tc].col;
}
